package inabit.fuzzy.data;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
/**Represents a fuzzy set containing operations to fuzzify and deffuzify
 * numeric values as well as intersection and union of fuzzy sets and
 * linguisit hedges modifiers
 * @author Rafael Rocha*/
public class FuzzySet {
	public static final double VERY = 2.0;
	public static final double NORMAL = 1;
	public static final double SLIGHTLY = 0.3;
	/**The number of elements at a fuzzy set*/
	public static final int ARRAY_SIZE = 100;

	private double xi, xf;
	private double[] values;
	private double maxValue;
	private String name;

	/**Returns a new Fuzzy Set which interval ranges from xi to xf without membership values*/
	public FuzzySet(String name, double xi, double xf) {
		this(name, xi, xf, new FuzzyFunction() {
			public double f(double x) {
				return 0;
			}
		});
	}

	/**Returns a new Fuzzy Set which interval ranges from xi to xf with membership values ranging
	 * from 0 to 1 and shape defined by the FuzzyFunction f*/
	public FuzzySet(String name, double xi, double xf, FuzzyFunction f) {
		this(name, xi, xf, f, 1);
	}

	/**Returns a new Fuzzy Set which interval ranges from xi to xf with membership values ranging
	 * from 0 to max and shape defined by the FuzzyFunction f*/
	public FuzzySet(String name, double xi, double xf, FuzzyFunction f,
			double max) {
		values = new double[ARRAY_SIZE];
		this.xi = xi;
		this.xf = xf;
		this.name = name;
		setupFunction(f, max);
	}

	public String getName() {
		return name;
	}

	public double getXi() {
		return xi;
	}

	public double getXf() {
		return xf;
	}

	public void setValues(double[] values) {

		if (values.length == this.values.length) {
			maxValue = 0;
			for (int i = 0; i < values.length; i++) {
				this.maxValue = Math.max(maxValue, values[i]);
			}
			this.values = values;
		}
	}

	/**The list of membership values of this set*/
	public double[] getValues() {
		return values;
	}
	/**Returns the bigger value of this set*/
	public double getMaxValue() {
		return maxValue;
	}

	// Min(xi, yi)
	/**Returns a new fuzzy set which represents the intersection of this set with the
	 * set passed as parameter*/
	public FuzzySet and(FuzzySet set) {
		if (set.getXi() == xi && set.getXf() == xf) {
			FuzzySet result = new FuzzySet("( " + name + " and "
					+ set.getName() + " )", xi, xf);
			double[] resultValues = new double[ARRAY_SIZE];
			double[] s1 = this.values;
			double[] s2 = set.getValues();
			for (int i = 0; i < ARRAY_SIZE; i++) {
				resultValues[i] = Math.min(s1[i], s2[i]);
			}
			result.setValues(resultValues);
			return result;
		}
		return null;
	}

	// Max(xi, yi)
	/**Returns a new fuzzy set which represents the union of this set with the
	 * set passed as parameter*/
	public FuzzySet or(FuzzySet set) {
		if (set.getXi() == xi && set.getXf() == xf) {
			FuzzySet result = new FuzzySet("( " + name + " or " + set.getName()
					+ " )", xi, xf);
			double[] resultValues = new double[ARRAY_SIZE];
			double[] s1 = values;
			double[] s2 = set.getValues();
			for (int i = 0; i < ARRAY_SIZE; i++) {
				resultValues[i] = Math.max(s1[i], s2[i]);
			}
			result.setValues(resultValues);
			return result;
		}
		return null;
	}

	// xi < alpha? 0 : xi
	/**Returns a new fuzzy set which cut the membership values smaller than alpha*/
	public FuzzySet alphaCut(double alpha) {
		FuzzySet result = new FuzzySet(name, xi, xf);
		double[] resultValues = new double[ARRAY_SIZE];
		for (int i = 0; i < ARRAY_SIZE; i++) {
			resultValues[i] = values[i] < alpha ? 0 : values[i];
		}
		result.setValues(resultValues);
		return result;
	}

	/**Creates a new fuzzy set which is a truncation of this set*/
	public FuzzySet truncate(double truncate) {
		DecimalFormat df = new DecimalFormat("#.##");
		FuzzySet result = new FuzzySet(name + "[" + df.format(truncate) + "]", xi, xf);
		double[] resultValues = new double[ARRAY_SIZE];
		for (int i = 0; i < ARRAY_SIZE; i++) {
			resultValues[i] = Math.min(truncate, values[i]);
		}
		result.setValues(resultValues);
		return result;
	}

	/**Creates a new Fuzzy set which is the negative of this set*/
	public FuzzySet negate() {
		FuzzySet set = new FuzzySet("not( " + name + " )", xi, xf);
		double[] resultValues = new double[ARRAY_SIZE];
		for (int i = 0; i < ARRAY_SIZE; i++) {
			resultValues[i] = 1 - values[i];
		}
		set.setValues(resultValues);
		return set;
	}

	/**Returns the membership value of x at this set*/
	public double fuzzify(double x) {
		int i = (int) Math.round((((x - xi) * ARRAY_SIZE) / (xf - xi)));
		if (i >= ARRAY_SIZE) {
			return 0;
		}
		if (i < 0) {
			return 0;
		}
		return values[i];
	}

	/**Apply a linguistic hedge to this fuzzy set, represented by a double which becomes
	 * the exponent of the membership value in this set.*/
	public FuzzySet applyHedge(double exp) {

		FuzzySet set = new FuzzySet(hedgeName(exp) + name, xi, xf);
		double[] resultValues = new double[ARRAY_SIZE];
		for (int i = 0; i < ARRAY_SIZE; i++) {
			resultValues[i] = Math.pow(values[i], exp);
		}
		set.setValues(resultValues);
		return set;
	}

	// int(x.g(x)dx) / (int g(x)dx)
	/**returns the value that best represent this set, using the centroid method*/
	public double deffuzify() {
		double centroid = 0;
		double mass = 0;
		for (int i = 0; i < ARRAY_SIZE; i++) {
			double x = (i / (double) ARRAY_SIZE) * (xf - xi) + xi;
			centroid += x * values[i];
			mass += values[i];
		}
		return centroid / mass;
	}

	/**Sets the membership value of a specified x at this fuzzy set*/
	public void setValue(double x, double y) {
		int i = (int) Math.round((((x - xi) * ARRAY_SIZE) / (xf - xi)));
		if (i >= ARRAY_SIZE) {
			i = ARRAY_SIZE - 1;
		}
		if (i < 0) {
			i = 0;
		}
		values[i] = Math.max(0, Math.min(1, y));
		if (values[i] > maxValue) {
			maxValue = values[i];
		}
	}

	/**Save this fuzzy set on the file*/
	public void saveOnFile(File file) throws IOException {
		BufferedWriter writer = new BufferedWriter(new FileWriter(file));

		writer.write(name + ":" + ARRAY_SIZE + ":" + xi + ", " + xf + ":");
		for (int i = 0; i < ARRAY_SIZE; i++) {
			if (i > 0) {
				writer.write(",");
			}
			writer.write("" + values[i]);
		}
		writer.close();
	}

	/**Loads the fuzzy set contained at the file*/
	public static FuzzySet loadFromFile(File file) throws Exception {
		BufferedReader reader = new BufferedReader(new FileReader(file));
		String line = reader.readLine();

		String[] valueInfo = line.split(":");
		String name = valueInfo[0];
		int size = Integer.parseInt(valueInfo[1]);
		if (size == ARRAY_SIZE) {
			String[] limitsInfo = valueInfo[2].split(",");
			double xi = Double.parseDouble(limitsInfo[0]);
			double xf = Double.parseDouble(limitsInfo[1]);
			String[] valueStrings = valueInfo[3].split(",");
			double[] values = new double[ARRAY_SIZE];
			for (int i = 0; i < size; i++) {
				values[i] = Double.parseDouble(valueStrings[i]);
			}
			FuzzySet set = new FuzzySet(name, xi, xf);
			set.setValues(values);
			reader.close();
			return set;
		} else {
			return null;
		}

	}

	/**Returns the fuzzy set from file and renames it to <i>newName</i>*/
	public static FuzzySet loadAndRename(File file, String newName)
			throws Exception {
		FuzzySet set = loadFromFile(file);
		set.name = newName;
		return set;
	}

	public FuzzySet copy() {
		FuzzySet set = new FuzzySet(name, xi, xf);
		set.setValues(getValues().clone());
		return set;
	}
	/**set the values accordingly to the function normalized so that maxCertainty
	is the max value*/
	public void setupFunction(FuzzyFunction function, double maxCertainty) {
		double max = 0;
		for (int i = 0; i < values.length; i++) {
			double x = ((i / (double) values.length)) * (xf - xi) + xi;

			values[i] = function.f(x);
			if (values[i] > max) {
				max = values[i];
			}
		}
		for (int i = 0; i < values.length; i++) {
			values[i] = (values[i] / max) * maxCertainty;
		}
		this.maxValue = maxCertainty;
	}
	/**set the values accordingly to the function normalized so that 1
	is the max value*/
	public void setupFunction(FuzzyFunction function) {
		double max = 0;
		for (int i = 0; i < values.length; i++) {
			double x = ((i / (double) values.length)) * (xf - xi) + xi;

			values[i] = function.f(x);
			if (values[i] > max) {
				max = values[i];
			}
		}
		for (int i = 0; i < values.length; i++) {
			values[i] = (values[i] / max);
		}
		this.maxValue = 1;
	}

	// hedge name
	private String hedgeName(double exp) {
		double normal = Math.abs(exp - NORMAL);
		double very = Math.abs(exp - VERY);
		double slightly = Math.abs(exp - SLIGHTLY);
		double min = Math.min(normal, Math.min(very, slightly));

		if (min == normal) {
			return "";
		} else if (min == slightly) {
			return "slightly ";
		} else {
			return "very ";
		}
	}
}
